package com.lk.common.utils.excel.util;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import com.lk.common.utils.excel.annotation.ExExcel;
import com.lk.common.utils.excel.annotation.ImExcel;
import com.lk.common.utils.excel.entity.Flag;

/**
 * Excel利用注解来进行导入导出
 * @author lijun
 * @since 2018-04-25
 */
public class ExcelUtil<T> {
	
	private static final Logger logger = LoggerFactory.getLogger(ExcelUtil.class);
	
	Class<T> clazz;  
	
    public ExcelUtil(Class<T> clazz) {  
        this.clazz = clazz;  
    }  
    
    /**
     * 根据注解进行Excel导入操作
     * @param sheetIndex 哪一个sheet
     * @param startRow 从哪一行开始读取数据
     * @throws Exception 
     */
    public List<T> importExcel(int sheetIndex, int startRow,MultipartFile file) throws Exception {  
        List<Integer> cols = new ArrayList<Integer>();	//excel中有哪些列是需要导出的
        List<T> list = new ArrayList<T>();  
        try {
        	Workbook workbook = getWorkBook(file);
            Sheet sheet = workbook.getSheetAt(sheetIndex);  
            int rows = sheet.getPhysicalNumberOfRows();  
            
            if (rows > 0) {
                List<Field> allFields = getMappedFiled(clazz, null,ImExcel.class); //得到所有有ImportExcel的域

                Map<Integer, Field> fieldsMap = new HashMap<Integer, Field>();//存放列的序号和field
                for (Field field : allFields) {  
                    if (field.isAnnotationPresent(ImExcel.class)) {  
                        ImExcel attr = field.getAnnotation(ImExcel.class);  
                        int col = getExcelCol(attr.col());// 获得列号  
                        cols.add(col);
                        field.setAccessible(true);// 设置类的私有字段属性可访问
                        fieldsMap.put(col, field);  
                    }  
                }  
                for (int i = startRow; i < rows; i++) {	//读取数据
                    Row row = sheet.getRow(i);
                    T entity = null;  
                    for (int j = 0; j < cols.size(); j++) {  
                        Cell cell = row.getCell(cols.get(j));  
                        if (cell == null) {  
                            continue;  
                        }  
                        int cellType = cell.getCellType();  
                        String c = "";  
                        if (cellType == HSSFCell.CELL_TYPE_NUMERIC) {  
                            c = String.valueOf(cell.getNumericCellValue());  
                        } else if (cellType == HSSFCell.CELL_TYPE_BOOLEAN) {  
                            c = String.valueOf(cell.getBooleanCellValue());  
                        } else {  
                            c = cell.getStringCellValue();  
                        }  
                        if (c == null || c.equals("")) {  
                            continue;  
                        }  
                        entity = (entity == null ? clazz.newInstance() : entity);// 如果不存在实例则新建 
                        Field field = fieldsMap.get(cols.get(j));
                        if (field==null) {  
                            continue;  
                        }  
                        // 取得类型,并根据对象类型设置值.  
                        Class<?> fieldType = field.getType();  
                        if (String.class == fieldType) {  
                            field.set(entity, String.valueOf(c));  
                        } else if ((Integer.TYPE == fieldType) || (Integer.class == fieldType)) {  
                            field.set(entity, Integer.parseInt(c));  
                        } else if ((Long.TYPE == fieldType) || (Long.class == fieldType)) {  
                            field.set(entity, Long.valueOf(c));  
                        } else if ((Float.TYPE == fieldType) || (Float.class == fieldType)) {  
                            field.set(entity, Float.valueOf(c));  
                        } else if ((Short.TYPE == fieldType) || (Short.class == fieldType)) {  
                            field.set(entity, Short.valueOf(c));  
                        } else if ((Double.TYPE == fieldType) || (Double.class == fieldType)) {  
                            field.set(entity, Double.valueOf(c));  
                        } else if (Character.TYPE == fieldType) {  
                            if ((c != null) && (c.length() > 0)) {  
                                field.set(entity, Character.valueOf(c.charAt(0)));  
                            }  
                        }  
                    }  
                    if (entity != null) {  
                        list.add(entity);  
                    }  
                }  
            }  
        } catch (Exception e) {  
        	logger.error("导入数据出错！");
            throw e; 
        } 
        return list;  
    } 
    
    /**
     * 读线程调用
     * @param sheetIndex 哪一个sheet
     * @param startRow 从哪一行开始读取数据
     * @throws Exception 
     */
    public void importExcelByThread(int sheetIndex, int startRow,MultipartFile file,ArrayBlockingQueue<T> queue,Flag flag) throws Exception {  
        List<Integer> cols = new ArrayList<Integer>();	//excel中有哪些列是需要导出的
        try {
        	Workbook workbook = getWorkBook(file);
            Sheet sheet = workbook.getSheetAt(sheetIndex);  
            int rows = sheet.getPhysicalNumberOfRows();  
            
            if (rows > 0) {
                List<Field> allFields = getMappedFiled(clazz, null,ImExcel.class); //得到所有有ImportExcel的域

                Map<Integer, Field> fieldsMap = new HashMap<Integer, Field>();//存放列的序号和field
                for (Field field : allFields) {  
                    if (field.isAnnotationPresent(ImExcel.class)) {  
                        ImExcel attr = field.getAnnotation(ImExcel.class);  
                        int col = getExcelCol(attr.col());// 获得列号  
                        cols.add(col);
                        field.setAccessible(true);// 设置类的私有字段属性可访问
                        fieldsMap.put(col, field);  
                    }  
                }  
                for (int i = startRow; i < rows; i++) {	//读取数据
                    Row row = sheet.getRow(i);
                    T entity = null;  
                    for (int j = 0; j < cols.size(); j++) {  
                        Cell cell = row.getCell(cols.get(j));  
                        if (cell == null) {  
                            continue;  
                        }  
                        int cellType = cell.getCellType();  
                        String c = "";  
                        if (cellType == HSSFCell.CELL_TYPE_NUMERIC) {  
                            c = String.valueOf(cell.getNumericCellValue());  
                        } else if (cellType == HSSFCell.CELL_TYPE_BOOLEAN) {  
                            c = String.valueOf(cell.getBooleanCellValue());  
                        } else {  
                            c = cell.getStringCellValue();  
                        }  
                        if (c == null || c.equals("")) {  
                            continue;  
                        }  
                        entity = (entity == null ? clazz.newInstance() : entity);// 如果不存在实例则新建 
                        Field field = fieldsMap.get(cols.get(j));
                        if (field==null) {  
                            continue;  
                        }  
                        // 取得类型,并根据对象类型设置值.  
                        Class<?> fieldType = field.getType();  
                        if (String.class == fieldType) {  
                            field.set(entity, String.valueOf(c));  
                        } else if ((Integer.TYPE == fieldType) || (Integer.class == fieldType)) {  
                            field.set(entity, Integer.parseInt(c));  
                        } else if ((Long.TYPE == fieldType) || (Long.class == fieldType)) {  
                            field.set(entity, Long.valueOf(c));  
                        } else if ((Float.TYPE == fieldType) || (Float.class == fieldType)) {  
                            field.set(entity, Float.valueOf(c));  
                        } else if ((Short.TYPE == fieldType) || (Short.class == fieldType)) {  
                            field.set(entity, Short.valueOf(c));  
                        } else if ((Double.TYPE == fieldType) || (Double.class == fieldType)) {  
                            field.set(entity, Double.valueOf(c));  
                        } else if (Character.TYPE == fieldType) {  
                            if ((c != null) && (c.length() > 0)) {  
                                field.set(entity, Character.valueOf(c.charAt(0)));  
                            }  
                        }  
                    }  
                    if (entity != null) {
                    	queue.put(entity); 
                    }  
                }  
            }
            flag.setReadDone(true);
            logger.debug("excel读取完成！");
        } catch(InterruptedException e) {
        	logger.error("线程中断异常！");
        	throw e;
        } catch (Exception e) {
        	logger.error("导入数据出错！");
            throw e; 
        } 
    } 
    
    /** 
     * 对list数据源将其里面的数据导入到excel表单 
     * @param sheetName 工作表的名称 
     * @param output 
     */  
    public ExcelUtil<T> exportExcel(List<T> list, String fileName,String sheetName,String version,HttpServletResponse response) {  
    	try {
    		Workbook workbook = null;
    		Sheet sheet = null;
    		if(version.equals("xls")) {
    			workbook = new HSSFWorkbook();
    		}else{
    			workbook = new XSSFWorkbook();
    		}
    		
    		//生成标头
    		sheet = workbook.createSheet();
    		Row row = sheet.createRow(0);
    		CellStyle style = workbook.createCellStyle();
    		style.setFillForegroundColor(HSSFColor.SKY_BLUE.index);  
            style.setFillBackgroundColor(HSSFColor.GREY_40_PERCENT.index);
    		List<Field> fields = getMappedFiled(clazz, null,ExExcel.class);
    		// 写入各个字段的列头名称  
            for (int i = 0; i < fields.size(); i++) {  
                Field field = fields.get(i);  
                ExExcel attr = field.getAnnotation(ExExcel.class);  
                int col = getExcelCol(attr.col());  
                Cell cell = row.createCell(col);  
                cell.setCellType(HSSFCell.CELL_TYPE_STRING);//设置列中写入内容为String类型  
                cell.setCellValue(attr.name());	//写入列名  
                cell.setCellStyle(style);  
             }  
             int startNo = 0;  
             int endNo = list.size();  
             // 写入各条记录,每条记录对应excel表中的一行  
             for (int i = startNo; i < endNo; i++) {  
                row = sheet.createRow(i + 1 - startNo);  
                T vo = (T) list.get(i); // 得到导出对象.  
                for (int j = 0; j < fields.size(); j++) {
                    Field field = fields.get(j);// 获得field.  
                    field.setAccessible(true);// 设置实体类私有属性可访问  
                    ExExcel attr = field.getAnnotation(ExExcel.class);  
                    try {  
                          Cell cell = row.createCell(getExcelCol(attr.col()));
                          cell.setCellType(HSSFCell.CELL_TYPE_STRING);  
                          cell.setCellValue(field.get(vo) == null ? "" : String.valueOf(field.get(vo)));  
                    } catch (IllegalArgumentException e) {  
                        e.printStackTrace();  
                    } catch (IllegalAccessException e) {  
                        e.printStackTrace();  
                    }  
                }  
             }  
            
            //导出
            response.reset();
            response.setContentType("application/octet-stream; charset=utf-8");
            response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
            ServletOutputStream out = response.getOutputStream();
            workbook.write(out);
    	}catch(Exception e) {
    		e.printStackTrace();
    	}
    	return this;
    }
    
    /*
	 * 得到workbook
	 */
	public Workbook getWorkBook(MultipartFile file) throws IOException {
		String fileName = file.getOriginalFilename();
		Workbook workbook = null;
		try {
			InputStream is = file.getInputStream();
			if (fileName.endsWith(".xls")) {
				//2003
				workbook = new HSSFWorkbook(is);
			} else if (fileName.endsWith(".xlsx")) {
				// 2007 及2007以上
				workbook = new XSSFWorkbook(is);
			}
		} catch (IOException e) {
			e.printStackTrace();
			throw new IOException();
		}
		return workbook;
	}
    
	
    /** 
     * 将EXCEL中A,B,C,D,E列映射成0,1,2,3 
     * @param col 
     */  
    public static int getExcelCol(String col) {  
        col = col.toUpperCase();  
        int count = -1;  
        char[] cs = col.toCharArray();  
        for (int i = 0; i < cs.length; i++) {  
            count += (cs[i] - 64) * Math.pow(26, cs.length - 1 - i);  
        }  
        return count;  
    }  
    
    /** 
     * 得到实体类所有通过注解映射了数据表的字段 
     * @param clazz
     * @param fields
     */  
    @SuppressWarnings({ "rawtypes" })
    private List<Field> getMappedFiled(Class clazz, List<Field> fields,Class excelType) {  
        if (fields == null) {  
            fields = new ArrayList<Field>();  
        }  

        Field[] allFields = clazz.getDeclaredFields();// 得到所有定义字段  
        // 得到所有field并存放到一个list中.  
        for (Field field : allFields) {  
            if (field.isAnnotationPresent(excelType)) { 
                fields.add(field);  
            }  
        }  
        if (clazz.getSuperclass() != null  
                && !clazz.getSuperclass().equals(Object.class)) {  
            getMappedFiled(clazz.getSuperclass(), fields,excelType);  
        }  

        return fields;  
    } 
    
}
